﻿/*
 * Copyright 2008 Lucifer Lu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using SilverChart.Core.Log;

namespace SilverChart {
    public abstract class CompositeChartWidget : Panel, ChartWidget {
        private static Logger logger = Logger.GetLogger<CompositeChartWidget>();

        private Dictionary<RelativeClientProvider, Rect> relativeRects;
        private Dictionary<RelativeClientProvider, Size> relativeSizes;

        public CompositeChartWidget() {
            relativeRects = new Dictionary<RelativeClientProvider, Rect>();
            relativeSizes = new Dictionary<RelativeClientProvider, Size>();
        }

        public virtual DockType Dock {
            get {
                return DockType.Client;
            }
        }

        public abstract String WidgetName { get; }

        private void Debug(String message) {
            if (logger.DebugLevel) {
                logger.Debug(String.Format("{0} - {1}", WidgetName, message));
            }
        }

        protected override System.Windows.Size MeasureOverride(Size availableSize) {
            Debug("MeasureOverride():" + availableSize.ToString());
            Size neededSize = new Size(0, 0);
            Size clientSize = new Size(0, 0);
            List<UIElement> clientUIElements = new List<UIElement>();

            foreach (UIElement element in Children) {
                Debug("Element:" + element);
                if (!(element is ChartWidget) || (element as ChartWidget).Dock == DockType.Client) {
                    Debug("Add to list");
                    clientUIElements.Add(element);
                }
                else {
                    //It is very strange. "as" operator will triger another layout invokation
                    //So try to avoid use "as" if element is not a ChartWidget derived class
                    ChartWidget widget = (ChartWidget)element;
                    Debug("widget:" + widget);
                    Debug("ready to measure");
                    widget.Measure(availableSize);

                    if (widget.DesiredSize.Width > clientSize.Width) {
                        Double deltaWidth = widget.DesiredSize.Width - clientSize.Width;
                        neededSize.Width += deltaWidth;
                        clientSize.Width += deltaWidth;
                    }

                    if (widget.DesiredSize.Height > clientSize.Height) {
                        Double deltaHeight = widget.DesiredSize.Height - clientSize.Height;
                        neededSize.Height += deltaHeight;
                        clientSize.Height += deltaHeight;
                    }

                    if (widget.Dock == DockType.Top || widget.Dock == DockType.Bottom) {
                        availableSize.Height -= widget.DesiredSize.Height;
                        clientSize.Height -= widget.DesiredSize.Height;
                    }
                    else if (widget.Dock == DockType.Left || widget.Dock == DockType.Right) {
                        availableSize.Width -= widget.DesiredSize.Width;
                        clientSize.Width -= widget.DesiredSize.Width;
                    }
                    Debug("Widget Type:" + widget.WidgetName);
                    Debug(String.Format("Widget DesiredSize Dimension:{0},{1}",
                        widget.DesiredSize.Width, widget.DesiredSize.Height));
                    Debug("Current Remain Size:" + availableSize.ToString());
                    Debug("Current Needed Size:" + neededSize.ToString());
                    Debug("Current Client Size:" + clientSize.ToString());
                }
            }

            foreach (UIElement element in clientUIElements) {
                RelativeClientProvider clientProvider = null;
                if (element is RelativeClientConsumer) {
                    clientProvider = ((RelativeClientConsumer)element).GetClientProvider();
                }

                if (clientProvider != null) {
                    if (!relativeSizes.ContainsKey((RelativeClientProvider)clientProvider)) {
                        throw new InvalidOperationException("RelativeClientProvider has not measured:" +
                            clientProvider.GetType().FullName);
                    }
                    element.Measure(clientProvider.GetRelativeClientSize(availableSize,
                        (RelativeClientConsumer)element));
                }
                else {
                    element.Measure(availableSize);
                    if (element.DesiredSize.Width > clientSize.Width) {
                        Double deltaWidth = element.DesiredSize.Width - clientSize.Width;
                        neededSize.Width += deltaWidth;
                        clientSize.Width += deltaWidth;
                    }

                    if (element.DesiredSize.Height > clientSize.Height) {
                        Double deltaHeight = element.DesiredSize.Height - clientSize.Height;
                        neededSize.Height += deltaHeight;
                        clientSize.Height += deltaHeight;
                    }

                    if (element is RelativeClientProvider) {
                        relativeSizes[(RelativeClientProvider)element] =
                            new Size(availableSize.Width, availableSize.Height);
                    }
                }

                Debug("Widget Type:" + (!(element is ChartWidget) ? "UIElement" :
                    (element as ChartWidget).WidgetName));
                Debug(String.Format("Widget DesiredSize Dimension:{0},{1}",
                    element.DesiredSize.Width, element.DesiredSize.Height));
                Debug("Current Needed Size:" + neededSize.ToString());
                Debug("Current Client Size:" + clientSize.ToString());
            }

            Debug("All Children DesiredSize:" + neededSize.ToString());

            return neededSize;
        }

        protected override Size ArrangeOverride(Size finalSize) {
            Debug("ArrangeOverride():" + finalSize.ToString());
            Double clientLeft = 0;
            Double clientTop = 0;
            Double clientWidth = finalSize.Width;
            Double clientHeight = finalSize.Height;
            List<UIElement> clientElements = new List<UIElement>();

            foreach (UIElement element in Children) {
                if (!(element is ChartWidget) || (element as ChartWidget).Dock == DockType.Client) {
                    clientElements.Add(element);
                }
                else {
                    ChartWidget widget = element as ChartWidget;
                    switch (widget.Dock) {
                        case DockType.Top:
                            widget.Arrange(new Rect(clientLeft, clientTop,
                                clientWidth, widget.DesiredSize.Height));
                            clientTop += widget.DesiredSize.Height;
                            clientHeight -= widget.DesiredSize.Height;
                            break;
                        case DockType.Left:
                            widget.Arrange(new Rect(clientLeft, clientTop,
                                widget.DesiredSize.Width, clientHeight));
                            clientLeft += widget.DesiredSize.Width;
                            clientWidth -= widget.DesiredSize.Width;
                            break;
                        case DockType.Right:
                            widget.Arrange(new Rect(clientWidth - widget.DesiredSize.Width, clientTop,
                                widget.DesiredSize.Width, clientHeight));
                            clientWidth -= widget.DesiredSize.Width;
                            break;
                        case DockType.Bottom:
                            widget.Arrange(new Rect(clientLeft,
                                clientTop + clientHeight - widget.DesiredSize.Height,
                                clientWidth, widget.DesiredSize.Height));
                            clientHeight -= widget.DesiredSize.Height;
                            break;
                    }
                    Debug("Widget Type:" + widget.WidgetName);
                    Debug(String.Format("Widget Actual Dimension:{0},{1}",
                        widget.ActualWidth, widget.ActualHeight));
                    Debug(String.Format("Widget Final Dimension:{0},{1}",
                        (widget as UIElement).DesiredSize.Width, (widget as UIElement).DesiredSize.Height));
                    Debug(String.Format("Current Client Size:{0},{1},{2},{3}",
                        clientTop, clientLeft, clientWidth, clientHeight));
                }
            }

            foreach (UIElement element in clientElements) {
                Debug("Ready to render client widget");
                Rect clientRect = new Rect(clientLeft, clientTop, clientWidth, clientHeight);
                RelativeClientProvider clientProvider = null;
                if (element is RelativeClientConsumer) {
                    clientProvider = ((RelativeClientConsumer)element).GetClientProvider();
                }

                if (clientProvider != null) {
                    if (!relativeRects.ContainsKey((RelativeClientProvider)clientProvider)) {
                        throw new InvalidOperationException("RelativeClientProvider has not arranged:" +
                            clientProvider.GetType().FullName);
                    }

                    Rect cosumerClientRect = CaculateRelativeRect(clientProvider,
                        clientProvider.GetRelativeClientArea(clientRect,
                        (RelativeClientConsumer)element));
                    element.Arrange(cosumerClientRect);
                }
                else {
                    element.Arrange(clientRect);
                    if (element is RelativeClientProvider) {
                        relativeRects[(RelativeClientProvider)element] = clientRect;
                    }
                }
            }

            return finalSize;
        }

        private Rect CaculateRelativeRect(RelativeClientProvider clientProvider, Rect innerRect) {
            Rect orgRect = relativeRects[clientProvider];
            Double marginLeft = 0;
            Double marginTop = 0;
            if (clientProvider is FrameworkElement) {
                marginLeft = ((FrameworkElement)clientProvider).Margin.Left;
                marginTop = ((FrameworkElement)clientProvider).Margin.Top;
            }
            return new Rect(orgRect.Left + innerRect.Left + marginLeft,
                orgRect.Top + innerRect.Top + marginTop, innerRect.Width, innerRect.Height);
        }

        public virtual void InitializeWidgets() {
            foreach (UIElement element in Children) {
                if (element is ChartWidget) {
                    ((ChartWidget)element).InitializeWidgets();
                }
            }
        }
    }
}
